/**
 * =============================================================================
 * L4D Health Glow (C)2011 Buster "Mr. Zero" Nielsen
 * =============================================================================
 *
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License, version 3.0, as 
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along 
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As a special exception, AlliedModders LLC gives you permission to link the
 * code of this program (as well as its derivative works) to "Half-Life 2,"
 * the "Source Engine," the "SourcePawn JIT," and any Game MODs that run on 
 * software by the Valve Corporation.  You must obey the GNU General Public
 * License in all respects for all other code used.  Additionally, 
 * AlliedModders LLC grants this exception to all derivative works.  
 * AlliedModders LLC defines further exceptions, found in LICENSE.txt 
 * (as of this writing, version JULY-31-2007), or 
 * <http://www.sourcemod.net/license.php>.
 */

/*
 * ==================================================
 *                    Preprocessor
 * ==================================================
 */

#define MAX_ENTITIES                    2048 // Max number of networked entities
#define GREEN "34 120 24 255"
#define WHITE "255 255 255 255"
#define CANCEL_BAR 0.0

#define ENTITY_DYNAMIC 0
#define ENTITY_PHYSIC 1

// UserMessageId for Fade.
new UserMsg:g_FadeUserMsgId;

/*
 * ==================================================
 *                     Public API
 * ==================================================
 */

/**
 * Executes cheat command on client.
 *
 * @param client        Client to execute cheat command, if not provided a 
 *                      random client will be picked.
 * @param command       Cheat command.
 * @param arguments     Arguments for command.
 * @return              True if executed, false otherwise.
 */
stock bool:CheatCommand(client = 0, const String:command[], const String:arguments[]="")
{
    if (client < 1 || client > MaxClients || !IsClientInGame(client))
    {
        client = GetAnyClient();
        if (client == -1) return false; // No players found to exec cheat cmd, return false
    }

    /* Apply root admin flag to user for compatiable with "Admin Cheats" plugin
     * by devicenull */
    new userFlags = GetUserFlagBits(client);
    SetUserFlagBits(client, ADMFLAG_ROOT);

    new flags = GetCommandFlags(command);
    SetCommandFlags(command, flags & ~FCVAR_CHEAT);
    FakeClientCommand(client, "%s %s", command, arguments);
    SetCommandFlags(command, flags);
    SetUserFlagBits(client, userFlags);

    return true;
}

/**
 * Wrapper for FindEntityByClassname to fall back on last valid entity.
 *
 * @param startEnt      The entity index after which to begin searching from. 
 *                      Use -1 to start from the first entity.
 * @param classname     Classname of the entity to find.
 * @return              Entity index >= 0 if found, -1 otherwise.
 */
stock FindEntityByClassnameEx(startEnt, const String:classname[])
{
    while (startEnt > -1 && !IsValidEntity(startEnt)) startEnt--;
    return FindEntityByClassname(startEnt, classname);
}

/**
 * Returns any ingame client.
 *
 * @param filterBots    Whether or not bots are also returned.
 * @return              Client index if found, -1 otherwise.
 */
stock GetAnyClient(bool:filterBots = false)
{
    FOR_EACH_CLIENT_IN_GAME(client)
    {
        if (filterBots && IsFakeClient(client)) continue;
        return client;
    }
    return -1;
}

/**
 * Returns the client count put in the server.
 *
 * @param inGameOnly    Whether or not connecting players are also counted.
 * @param fliterBots    Whether or not bots are also counted.
 * @return              Client count in the server.
 */
stock GetClientCountEx(bool:inGameOnly, bool:filterBots)
{
    new clients = 0;
    FOR_EACH_CLIENT_CONNECTED(client)
    {
        if (inGameOnly && !IsClientInGame(client)) continue;
        if (filterBots && IsFakeClient(client)) continue;
        clients++;
    }
    return clients;
}

/**
 * Retrives entity's absolute origin.
 *
 * @param entity        Entity index.
 * @param origin        Destination vector buffer to store origin in.
 * @noreturn
 */
stock GetEntityAbsOrigin(entity, Float:origin[3])
{
    if (entity < 1 || !IsValidEntity(entity)) return;

    decl Float:mins[3], Float:maxs[3];
    GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin);
    GetEntPropVector(entity, Prop_Send, "m_vecMins", mins);
    GetEntPropVector(entity, Prop_Send, "m_vecMaxs", maxs);

    for (new i = 0; i < 3; i++)
    {
        origin[i] += (mins[i] + maxs[i]) * 0.5;
    }
}

/**
 * Returns whether translation file is valid and readable.
 *
 * @param name          Name of translation file.
 * @return              True if valid, false otherwise.
 */
stock bool:IsTranslationValid(const String:name[])
{
    decl String:path[PLATFORM_MAX_PATH], Handle:file;
    BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s.txt", name);
    if (!FileExists(path, false))
    {
        return false;
    }
    else if ((file = OpenFile(path, "r")) == INVALID_HANDLE)
    {
        return false;
    }
    else
    {
        CloseHandle(file);
        return true;
    }
}

/**
 * Converts string to lower case.
 * Cant convert multibyte characters.
 *
 * @param string        String to convert.
 * @param len           String length (includes null terminator).
 * @noreturn
 */
stock StringToLower(String:string[], len)
{
    len--;
    new i = 0;

    for (i = 0; i < len; i++)
    {
        if (string[i] == '\0') break;
        string[i] = CharToLower(string[i]);
    }

    string[i] = '\0';
}

/**
 * Sends a SayText2 usermessage to a client
 *
 * @param client        Client index.
 * @param author        Author index.
 * @param message       Message.
 * @noreturn
 */
stock SayText2(client, author, const String:format[], any:...)
{
    decl String:buffer[256];
    VFormat(buffer, 256, format, 4);

    new Handle:hBuffer = StartMessageOne("SayText2", client);
    BfWriteByte(hBuffer, author);
    BfWriteByte(hBuffer, true);
    BfWriteString(hBuffer, buffer);
    EndMessage();
}

/*debuging facility */
stock sub_debug(const String:Message[], any:...)
{
	decl String:DebugBuff[128];
	VFormat(DebugBuff, sizeof(DebugBuff), Message, 2);
	LogMessage(DebugBuff);

}

stock GetAimVector(client, Float:AimVector[3])
{
	decl Float:HeadingVector[3];

	GetClientEyeAngles(client, HeadingVector);
	
	AimVector[0] = Cosine(DegToRad(HeadingVector[1]));
	AimVector[1] = Sine(DegToRad(HeadingVector[1]));
	AimVector[2] = 1.0;
}

stock Max(a, b)
{

	return a >= b ? a : b;
}

stock bool:CreateDrug(client, Float:angle)
{

	if (IsValidClientDead(client))
	{
		KillDrug(client);
		return false;
	}

	DispatchKeyValue(client, "rendercolor", GREEN); //display green player

	new Float:angs[3];
	GetClientEyeAngles(client, angs);
	
	//	angs[0] = GetRandomFloat(1.0,rotate);
	//	angs[1] = GetRandomFloat(1.0,rotate);
	angs[2] = angle;

	//can make negative angle
	//	angs[0] *= GetRandomInt(0,1) == 0 ? -1 : 1;
	//	angs[1] *= GetRandomInt(0,1) == 0 ? -1 : 1;
	angs[2] *= GetRandomInt(0,1) == 0 ? -1 : 1;
	
	TeleportEntity(client, NULL_VECTOR, angs, NULL_VECTOR);
	
	new clients[2];
	clients[0] = client;	
	
	new Handle:message = StartMessageEx(g_FadeUserMsgId, clients, 1);
	BfWriteShort(message, 1536);
	BfWriteShort(message, 1536);
	BfWriteShort(message, (0x0008));
	BfWriteByte(message, 0);
	BfWriteByte(message, 100);
	BfWriteByte(message, 0);
	BfWriteByte(message, 128);
	
	EndMessage();	

	return true;
}

stock bool:KillDrug(client)
{

	if(!IsValidClient(client))
	{
		return false;
	}

	new Float:angs[3];
	GetClientEyeAngles(client, angs);
	
	//	angs[0] = 0.0;
	//	angs[1] = 0.0;
	angs[2] = 0.0;
	
	TeleportEntity(client, NULL_VECTOR, angs, NULL_VECTOR);	

	new clients[2];
	clients[0] = client;	
	
	new Handle:message = StartMessageEx(g_FadeUserMsgId, clients, 1);
	BfWriteShort(message, 1536);
	BfWriteShort(message, 1536);
	BfWriteShort(message, (0x0001 | 0x0010));
	BfWriteByte(message, 0);
	BfWriteByte(message, 0);
	BfWriteByte(message, 0);
	BfWriteByte(message, 0);
	EndMessage();	

	DispatchKeyValue(client, "rendercolor", WHITE); //display normal player
	
	return true;
}


stock drug_init()
{
	g_FadeUserMsgId = GetUserMessageId("Fade");
}

stock create_entity(any:type, String:model[], Float:pos[3], Float:angle[3], bool:solid)
{
	new prop;

	if(type == ENTITY_DYNAMIC)
	{
		prop = CreateEntityByName("prop_dynamic");
	}
	else if(type == ENTITY_PHYSIC)
	{
		prop = CreateEntityByName("prop_physics");
	}
	else
	{
		return -1;
	}

        DispatchKeyValue(prop, "model", model);
        DispatchSpawn(prop);
	AcceptEntityInput(prop, "EnableCollision");
	DispatchKeyValue(prop, "solid", "6");
	SetEntProp(prop, Prop_Send, "m_usSolidFlags", 4096);
	SetEntProp(prop, Prop_Send, "m_nSolidType", 6);
	SetEntProp(prop, Prop_Send, "movetype", 6);
	SetEntProp(prop, Prop_Send, "movecollide", 0);

	if(solid)
	{
		SetEntProp(prop, Prop_Send, "m_CollisionGroup", 0);
	}
	else
	{
		SetEntProp(prop, Prop_Send, "m_CollisionGroup", 11);
	}

//        SetEntityMoveType(prop, MOVETYPE_VPHYSICS);

        DispatchKeyValue(prop, "StartDisabled", "false");

	TeleportEntity(prop, pos, angle, NULL_VECTOR);

	return prop;

}

stock bool:IsPlayerIncapped(client)
{
	if (GetEntProp(client, Prop_Send, "m_isIncapacitated", 1)
	|| (GetEntProp(client, Prop_Send, "m_isHangingFromLedge") != 0))
	{
		return true;
	}
	
	return false;
}

stock bool:IsBeingRevived(client)
{
	return IsValidClient(GetEntPropEnt(client, Prop_Send, "m_reviveOwner"));
}

stock TeamPlayersTotal(any:team)
{
	new int=0;
	if(team == TEAM_SURVIVORS)
	{
		FOR_EACH_SURVIVOR(i)
		{
			int++;
		}
	}

	if(team == TEAM_INFECTED)
	{
		FOR_EACH_INFECTED(i)
		{
			int++;
		}
	}

	return int;
}

stock GetAnySurvivorBot()
{
	FOR_EACH_SURVIVOR_BOT(bot)
	{
		return bot;
	}

	return -1;
}

stock GetAnySurvivor()
{
	FOR_EACH_SURVIVOR(client)
	{
		return client;
	}

	return -1;
}

stock Float:get_distance_client_entity(client, entity)
{
	/*calculate distance */
	decl Float:ent_pos[3];
	decl Float:player_pos[3];
	decl Float:dist_vec[3];

	GetEntityAbsOrigin(entity, ent_pos);
	GetClientAbsOrigin(client, player_pos);
	MakeVectorFromPoints(ent_pos, player_pos, dist_vec);
	new Float:dist = GetVectorLength(dist_vec);

	return dist;
}

stock Load_Unload_ProgressBar(victim, Float:time)
{
	SetEntPropFloat(victim, Prop_Send, "m_flProgressBarStartTime", GetGameTime());
	SetEntPropFloat(victim, Prop_Send, "m_flProgressBarDuration", time);
}

/**
* @brief Will return a survivors health, including also the temporal health.
*
* @client            Client index to retrieve the health from.
*
* @return            Returns the client's health as an integer.
* @error             Will return -1 if there was any problem.
*/
stock GetClientRealHealth(client)
{
    //First filter -> Must be a valid client, successfully in-game and not an spectator (The dont have health).
    if(!client
    || !IsValidEntity(client)
    || !IsClientInGame(client)
    || !IsPlayerAlive(client)
    || IsClientObserver(client))
    {
        return -1;
    }
    
    //If the client is not on the survivors team, then just return the normal client health.
    if(GetClientTeam(client) != 2)
    {
        return GetClientHealth(client);
    }
    
    //First, we get the amount of temporal health the client has
    new Float:buffer = GetEntPropFloat(client, Prop_Send, "m_healthBuffer");
    
    //We declare the permanent and temporal health variables
    new Float:TempHealth;
    new PermHealth = GetClientHealth(client);
    
    //In case the buffer is 0 or less, we set the temporal health as 0, because the client has not used any pills or adrenaline yet
    if(buffer <= 0.0)
    {
        TempHealth = 0.0;
    }
    
    //In case it is higher than 0, we proceed to calculate the temporl health
    else
    {
        //This is the difference between the time we used the temporal item, and the current time
        new Float:difference = GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime");
        
        //We get the decay rate from this convar (Note: Adrenaline uses this value)
        new Float:decay = GetConVarFloat(FindConVar("pain_pills_decay_rate"));
        
        //This is a constant we create to determine the amount of health. This is the amount of time it has to pass
        //before 1 Temporal HP is consumed.
        new Float:constant = 1.0/decay;
        
        //Then we do the calcs
        TempHealth = buffer - (difference / constant);
    }
    
    //If the temporal health resulted less than 0, then it is just 0.
    if(TempHealth < 0.0)
    {
        TempHealth = 0.0;
    }
    
    //Return the value
    return RoundToFloor(PermHealth + TempHealth);
}  

/*this function turn head of client to target (yes it an aimbot:)*/
stock turn_head_to(client, target)
{
	if(IsValidClient(client) && IsValidClient(target))
	{
		decl Float:eyepos[3];
		decl Float:targetpos[3];
		decl Float:aimtotarget[3];
		decl Float:aimangle[3];

		GetClientEyePosition(client, eyepos);
		GetClientAbsOrigin(target, targetpos);
		MakeVectorFromPoints(eyepos, targetpos, aimtotarget);
		GetVectorAngles(aimtotarget, aimangle);
		TeleportEntity(client, NULL_VECTOR, aimangle, NULL_VECTOR);
	}

}

stock Require_L4D2()
{
	decl String:game[32];
	GetGameFolderName(game, sizeof(game));
	if (!StrEqual(game, "left4dead2", false))
	{
		SetFailState("Plugin supports Left 4 Dead 2 only.");
	}
}

stock PrintToChatAllExclude(client1, client2, const String:format[], any:...)
{

	decl String:buffer[256];
	VFormat(buffer, 256, format, 4);

	FOR_EACH_HUMAN(user)
	{
		if(user != client1 && user != client2)
		{

			new Handle:hBuffer = StartMessageOne("SayText2", user);
			BfWriteByte(hBuffer, 0);
			BfWriteByte(hBuffer, true);
			BfWriteString(hBuffer, buffer);
			EndMessage();

		}
	}
}

stock PrintToChatOnly(client1, client2, const String:format[], any:...)
{

	decl String:buffer[256];
	VFormat(buffer, 256, format, 4);

	FOR_EACH_HUMAN(user)
	{
		if(user == client1 || user == client2)
		{

			new Handle:hBuffer = StartMessageOne("SayText2", user);
			BfWriteByte(hBuffer, 0);
			BfWriteByte(hBuffer, true);
			BfWriteString(hBuffer, buffer);
			EndMessage();

		}
	}
}

stock ResetInfectedAbility(client, Float:time)
{
	if (IsValidInfectedAlive(client))
	{
		new ability = GetEntPropEnt(client, Prop_Send, "m_customAbility");
		if (ability > 0)
		{
			SetEntPropFloat(ability, Prop_Send, "m_duration", time);
			SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + time);
		}
	}
}